#pragma once

#include "../detail/box_alloc.hh"
#include "singleton.hh"
#include <algorithm>
#include <cstdlib>
#include <list>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

#define USE_OBJECT_POOL

namespace kratos {

// Object pool interface
class _BoxObjectPoolBase {
public:
  virtual ~_BoxObjectPoolBase() {}
  // Returns the allocated object's count
  // @return The allocated object's count
  virtual std::size_t getCount() const = 0;
  // Returns std::type_info of 'ObjectType'
  // @return std::type_info of 'ObjectType'
  virtual const std::type_info &getTypeInfo() const = 0;
  // Returns the size in byte of 'ObjectType'
  virtual std::size_t getObjectSize() const = 0;
};

using PoolList = std::list<_BoxObjectPoolBase *>;

// Object pool manager
class _BoxObjectPoolManager {
  PoolList poolList_; // object pool list

public:
  // dtor
  ~_BoxObjectPoolManager() {
    PoolList temp = poolList_;
    for (auto pool : temp) {
      delete pool;
    }
  }
  // Returns pools
  const PoolList getPools() { return poolList_; }

private:
  // Adds a pool singleton pointer
  void add(_BoxObjectPoolBase *pool) { poolList_.push_back(pool); }
  // Removes pool singleton pointer
  void remove(_BoxObjectPoolBase *pool) {
    poolList_.erase(std::remove(poolList_.begin(), poolList_.end(), pool));
  }

private:
  template <typename ObjectType, size_t Count> friend class _BoxObjectPool;
};

extern kratos::_BoxObjectPoolManager *get_global_box_object_pool_manager();

#define BoxObjectPoolManagerRef (*get_global_box_object_pool_manager())

// object pool
// ObjectType object type
// Count pre-allocated count of object for each expand
template <typename ObjectType, size_t Count = 1>
class _BoxObjectPool : public _BoxObjectPoolBase {
  _BoxObjectPool(const _BoxObjectPool &) = delete;
  _BoxObjectPool(_BoxObjectPool &&) = delete;
  _BoxObjectPool<ObjectType> &operator=(const _BoxObjectPool &) = delete;

private:
  std::size_t allocatedCount_; // occupied object count

public:
  // ctor
  _BoxObjectPool() : allocatedCount_(0) {
    BoxObjectPoolManagerRef.add(this);
  }

  // dtor
  virtual ~_BoxObjectPool() {
  }

  // Create a instance of 'ObjectType'
  // @param args The arguments for ctor of ObjectType
  // @return The instance pointer of ObjectType
  template <typename... Args> ObjectType *createObject(Args... args) {
#ifndef USE_OBJECT_POOL
    allocatedCount_ += 1;
    return new ObjectType(std::forward<Args>(args)...);
#else
    char *memoryPtr = kratos::service::box_malloc(sizeof(ObjectType));
    allocatedCount_ += 1;
    auto objectPtr = new (memoryPtr) ObjectType(std::forward<Args>(args)...);
    return objectPtr;
#endif // !USE_OBJECT_POOL
  }

  // Recycling
  // @param objectPtr The pointer of 'ObjectType'
  void disposeObject(ObjectType *objectPtr) {
#ifndef USE_OBJECT_POOL
    allocatedCount_ -= 1;
    delete objectPtr;
#else
    allocatedCount_ -= 1;
    objectPtr->~ObjectType();
    kratos::service::box_free(reinterpret_cast<void*>(objectPtr));
#endif // !USE_OBJECT_POOL
  }

  // Returns the allocated object's count
  // @return The allocated object's count
  virtual size_t getCount() const override { return allocatedCount_; }

  // Returns std::type_info of 'ObjectType'
  // @return std::type_info of 'ObjectType'
  virtual const std::type_info &getTypeInfo() const override {
    return typeid(ObjectType);
  }

  // Returns the size in byte of 'ObjectType'
  virtual std::size_t getObjectSize() const override {
    return sizeof(ObjectType);
  }
};

template <typename ObjectType> static inline void DestroyBoxObjectPool() {
  kratos::Singleton<kratos::_BoxObjectPool<ObjectType>>::destroy();
}

template <typename ObjectType>
static inline kratos::_BoxObjectPool<ObjectType> *getBoxObjectPoolPtr() {
  return kratos::Singleton<kratos::_BoxObjectPool<ObjectType>>::instance();
}

template <typename ObjectType>
static inline kratos::_BoxObjectPool<ObjectType> &getBoxObjectPoolRef() {
  return *(kratos::Singleton<kratos::_BoxObjectPool<ObjectType>>::instance());
}

// Returns a object from pool and constructs it
// ObjectType The prototype of object
// Args The argument of object's constructor
// @return The new created object's pointer
template <typename ObjectType, typename... Args>
static inline ObjectType *allocate(Args... args) {
  return getBoxObjectPoolRef<ObjectType>().createObject(
      std::forward<Args>(args)...);
}

// Returns a object from pool and constructs it
// ObjectType The prototype of object
// Args The argument of object's constructor
// @return The new created object's reference
template <typename ObjectType, typename... Args>
static inline ObjectType &allocateRef(Args... args) {
  auto object = getBoxObjectPoolRef<ObjectType>().createObject(
      std::forward<Args>(args)...);
  if (!object) {
    throw std::runtime_error("out of memory");
  }
  return *object;
}

// Recycles to object pool
// @param objectPtr Object pointer
template <typename ObjectType>
static inline void box_dispose(ObjectType *objectPtr) {
  getBoxObjectPoolRef<ObjectType>().disposeObject(objectPtr);
}

// Recycles to object pool
// @param objectPtr Object reference
template <typename ObjectType>
static inline void box_dispose(ObjectType &objectRef) {
  box_dispose(&objectRef);
}

// Destructor for unique pointer
template <typename T> class UniquePtrDeleter {
public:
  void operator()(T *obj) {
    if (!obj) {
      return;
    }
    box_dispose(dynamic_cast<T *>(obj));
  }
};
// Destructor for char*
template <> class UniquePtrDeleter<char> {
public:
  void operator()(char *obj) { kratos::service::box_free(obj); }
};
// Destructor for smart pointer
template <typename T> void shared_ptr_deleter(T *obj) {
#ifdef USE_OBJECT_POOL
  if (!obj) {
    return;
  }
  auto *typeObject = dynamic_cast<T *>(obj);
  box_dispose(typeObject);
#endif // USE_OBJECT_POOL
}

template <typename T>
using unique_pool_ptr = std::unique_ptr<T, UniquePtrDeleter<T>>;

template <typename T, typename... ARGS>
inline unique_pool_ptr<T> make_unique_pool_ptr(ARGS... args) {
  auto *ptr = allocate<T>(std::forward<ARGS>(args)...);
  return unique_pool_ptr<T>(ptr);
}

template <>
inline unique_pool_ptr<char> make_unique_pool_ptr(std::size_t size) {
    auto* ptr = kratos::service::box_malloc(size);
    return unique_pool_ptr<char>(ptr);
}

template <>
inline unique_pool_ptr<char> make_unique_pool_ptr(int size) {
    auto* ptr = kratos::service::box_malloc(size);
    return unique_pool_ptr<char>(ptr);
}

template <>
inline unique_pool_ptr<char> make_unique_pool_ptr(short size) {
    auto* ptr = kratos::service::box_malloc(size);
    return unique_pool_ptr<char>(ptr);
}

template <>
inline unique_pool_ptr<char> make_unique_pool_ptr(long size) {
    auto* ptr = kratos::service::box_malloc(size);
    return unique_pool_ptr<char>(ptr);
}

template <typename T, typename... ARGS>
inline std::shared_ptr<T> make_shared_pool_ptr(ARGS... args) {
#ifdef USE_OBJECT_POOL
  return std::shared_ptr<T>(allocate<T>(std::forward<ARGS>(args)...),
                            shared_ptr_deleter<T>);
#else
  return std::shared_ptr<T>(new T(std::forward<ARGS>(args)...));
#endif
}

} // namespace kratos

using BoxObjectPoolManager = kratos::_BoxObjectPoolManager;

#define BoxObjectPoolSingleton(T) kratos::Singleton<kratos::_BoxObjectPool<T>>
